package com.hwtx.form.domain.def;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.util.FormUtil;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.hwtx.form.domain.def.FormProperties.*;
import static com.hwtx.form.util.JacksonUtil.getJsonNodeParent;

@Slf4j
public class FormValidateDef {

    final Map<String, List<ValidateAction>> itemValidationAction = Maps.newHashMap();
    final Map<String, List<FormValidate>> customerFormValidations = Maps.newHashMap();
    static final String validatePrefix = "validate.";
    static final String validateActionSuffix = "Predicate";
    static final String predicateClassPrefix = FormValidation.class.getName() + "$";

    private List<FormValidateItem> formValidateItems;

    @Data
    public static class FormValidateItem {
        private String type;
        private String name;
        private String label;
        private Boolean required = false;
        private ValidationsDef validationsDef;
        private Integer precision;
    }

    @Builder
    @Getter
    static class ValidateAction {
        FromValidatePredicate<Object> predicate;
        String msg;

        public String getMsg() {
            return String.format(msg, predicate.getRuleValue());
        }
    }

    @Data
    public static class ValidationsDef {
        private Boolean isAlpha;
        private Integer minLength;
        private Integer maxLength;
        private Boolean isNumeric;
        private Integer maximum;
        private Integer minimum;
        private Boolean isPhoneNumber;
        private Boolean isDbName;
        private Boolean isEmail;
    }

    @Builder
    @Getter
    public static class ValidationResult {
        private String message;
        private boolean pass;
        private String key;
    }

    public Map<String, FormValidateItem> getValidateItems() {
        return formValidateItems.stream().filter(item -> StringUtils.isNotEmpty(item.getName())).filter(item -> !"id".equalsIgnoreCase(item.getName())).collect(Collectors.toMap(FormValidateItem::getName, Function.identity()));
    }

    public void buildFormValidateItem(String content) {
        List<JsonNode> nameNodes = getJsonNodeParent(content, FORM_ITEM_NAME);
        Properties properties = new Properties();
        try {
            properties.load(getClass().getClassLoader().getResourceAsStream("formValidate.properties"));
        } catch (Exception e) {
            log.error("", e);
        }
        formValidateItems = nameNodes.stream().filter(node -> {
            JsonNode formItem = node.get(FORM_ITEM_TYPE);
            if (formItem != null && (formItem.asText().equals(FORM_ITEM_HIDDEN) || formItem.asText().equals(FORM_TYPE))) {
                return false;
            }
            formItem = node.get(FORM_ITEM_HIDDEN);
            return formItem == null || !formItem.asBoolean();
        }).map(node -> {
            FormValidateItem formItem = new FormValidateItem();
            if (node.get(FORM_ITEM_TYPE) != null) {
                formItem.setType(node.get(FORM_ITEM_TYPE).asText());
            }
            if (node.get(FORM_ITEM_NAME) != null) {
                formItem.setName(node.get(FORM_ITEM_NAME).asText());
            }
            if (node.get(FORM_ITEM_LABEL) != null) {
                formItem.setLabel(node.get(FORM_ITEM_LABEL).asText());
            }
            if (node.get(FORM_ITEM_PRECISION) != null) {
                formItem.setPrecision(node.get(FORM_ITEM_PRECISION).asInt());
            }
            if (node.get(FORM_ITEM_REQUIRED) != null) {
                formItem.setRequired(node.get(FORM_ITEM_REQUIRED).asBoolean());
            }
            formItem.setValidationsDef(buildValidation(node.get(FORM_ITEM_VALIDATION), formItem.getName(), formItem.getRequired(), properties));
            return formItem;
        }).collect(Collectors.toList());
    }

    public void buildCustomerValidations(FormDef formDef) {
        Map<String, FormValidate> formValidates = SpringUtil.getBeansOfType(FormValidate.class);
        formValidates.values().forEach(formValidate -> {
            com.hwtx.form.annotation.FormValidation formValidation = formValidate.getClass().getAnnotation(com.hwtx.form.annotation.FormValidation.class);
            if (formValidation != null) {
                if (!formValidation.enable()) {
                    return;
                }
                String[] targetForms = formValidation.formId();
                if (ArrayUtils.isNotEmpty(targetForms) && Sets.newHashSet(targetForms).contains(formDef.getFormId().toString())) {
                    setCustomerFormValidations(formValidation, formValidate);
                    return;
                }
                String[] formTypes = formValidation.formType();
                if (ArrayUtils.isNotEmpty(formTypes) && Sets.newHashSet(formTypes).contains(formDef.getType())) {
                    setCustomerFormValidations(formValidation, formValidate);
                }
            }
        });
    }

    private void setCustomerFormValidations(com.hwtx.form.annotation.FormValidation formValidation, FormValidate formValidate) {
        for (String key : formValidation.key()) {
            customerFormValidations.compute(key, (k, v) -> {
                if (v == null) {
                    v = Lists.newArrayList();
                }
                v.add(formValidate);
                return v;
            });
        }
    }

    private ValidationsDef buildValidation(JsonNode validationNode, String itemName, boolean itemRequired, Properties properties) {
        ValidationsDef validationsDef = new ValidationsDef();
        if (validationNode == null) {
            return validationsDef;
        }
        try {
            Field[] fields = ValidationsDef.class.getDeclaredFields();
            Map<String, Field> fieldNameMapping = Stream.of(fields).collect(Collectors.toMap(Field::getName, Function.identity()));
            List<ValidateAction> actions = Lists.newArrayList();
            for (Iterator<Map.Entry<String, JsonNode>> it = validationNode.fields(); it.hasNext(); ) {
                Map.Entry<String, JsonNode> entry = it.next();
                String ruleName = entry.getKey();
                Field field = fieldNameMapping.get(ruleName);
                if (field == null) {
                    throw new RuntimeException("验证规则【" + ruleName + "】不存在");
                }
                String msg = (String) properties.get(validatePrefix + ruleName);
                if (StringUtils.isEmpty(msg)) {
                    throw new RuntimeException("未能获取验证规则【" + ruleName + "】对应的验证提示消息");
                }
                String predicateClass = predicateClassPrefix + FormUtil.firstCharToUpperCase(ruleName) + validateActionSuffix;
                Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(predicateClass);
                Constructor<?> constructor = clazz.getDeclaredConstructor(field.getType());
                Object defValue = entry.getValue().asText();
                field.setAccessible(true);
                if (field.getType().equals(Boolean.class)) {
                    defValue = entry.getValue().asBoolean();
                } else if (field.getType().equals(Integer.class)) {
                    defValue = entry.getValue().intValue();
                } else if (field.getType().equals(Long.class)) {
                    defValue = entry.getValue().longValue();
                }
                if (defValue != null) {
                    field.set(validationsDef, defValue);
                }
                Object predicateObject = constructor.newInstance(defValue);
                actions.add(ValidateAction.builder().msg(msg).predicate((FromValidatePredicate<Object>) predicateObject).build());
            }
            itemValidationAction.put(itemName, actions);
            if (itemRequired) {
                actions = itemValidationAction.get(itemName);
                if (actions == null) {
                    actions = Lists.newArrayList();
                }
                String msg = (String) properties.get(validatePrefix + "isRequired");
                actions.addFirst(ValidateAction.builder().msg(msg).predicate(new FormValidation.RequiredPredicate()).build());
            }
        } catch (Exception e) {
            log.error("加载验证提示消息文件失败", e);
            throw new RuntimeException(e);
        }
        return validationsDef;
    }

    public ValidationResult validateForm(FormDef formDef, String name, Object value) {
        ValidationResult validateDefault = validateDefault(name, value);
        if (!validateDefault.isPass()) {
            return validateDefault;
        }
        List<FormValidate> formValidates = customerFormValidations.get(name);
        if (formValidates != null) {
            for (FormValidate formValidate : formValidates) {
                ValidationResult validationResult = formValidate.validate(formDef, name, value);
                if (!validationResult.isPass()) {
                    return validationResult;
                }
            }
        }
        return ValidationResult.builder().pass(true).message("验证通过").key(name).build();
    }

    private ValidationResult validateDefault(String name, Object value) {
        List<ValidateAction> actions = itemValidationAction.get(name);
        if (actions == null) {
            return ValidationResult.builder().pass(true).key(name).build();
        }
        for (ValidateAction validateAction : actions) {
            boolean ret = validateAction.getPredicate().test(value);
            if (!ret) {
                return ValidationResult.builder().pass(false).message(validateAction.getMsg()).key(name).build();
            }
        }
        return ValidationResult.builder().pass(true).message("验证通过").key(name).build();
    }
}
